package com.aco.fatureapp.utilitary;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.os.Build;
import android.os.NetworkOnMainThreadException;
import android.util.Log;

import com.aco.fatureapp.enums.EnumProgress;
import com.aco.fatureapp.model.ConfiguracaoApp;

public class JSonHttpRequest {

	public static final String SERVICO_INDISPONIVEL = "Serviço temporariamente indisponível, "
			+ "tente novamente mais tarde ou entre em contato com o administrador do sistema.";

	public static final String ERRO_GENERICO = "Ocorreu uma falha inesperada, "
			+ "por favor entre em contato com o administrador do sistema.";

	public static final String SYNC_SUCCESSFUL = "Sincronização realizada com sucesso!";

	public static final int REQUEST_TYPE_IMPORT = 1;

	public static final int REQUEST_TYPE_EXPORT = 2;

	public static final int REQUEST_TYPE_ACCESS = 3;

	public static final int REQUEST_TYPE_CLOSE = 4;

	static ConfiguracaoApp sConfiguracaoApp = null;

	/**
	 *
	 * Enumeration that represents the kind of request.
	 *
	 */
	public enum EnumTipoRequisicao {
		GET, POST;
	}

	public static String getImportURL() {

		if (sConfiguracaoApp == null) {
			sConfiguracaoApp = ConfiguracaoApp.findAll(ConfiguracaoApp.class)
					.next();
		}

		return "http://" + sConfiguracaoApp.getIpConexao() + ":"
				+ sConfiguracaoApp.getPortaDeConecao() + "/"
				+ sConfiguracaoApp.getNomeServicoWeb() + "/";

		// return "http://192.168.0.10:8080/iFatureWeb/rest/importBean/";
	}

	public static String getExportURL() {
		if (sConfiguracaoApp == null) {
			sConfiguracaoApp = ConfiguracaoApp.findAll(ConfiguracaoApp.class)
					.next();
		}

		return "http://" + sConfiguracaoApp.getIpConexao() + ":"
				+ sConfiguracaoApp.getPortaDeConecao()
				+ "/ifature/rest/exportBean/";

		// return "http://192.168.0.10:8080/iFature/rest/exportBean/";
	}

	public static String getClouseRouteURL() {
		if (sConfiguracaoApp == null) {
			sConfiguracaoApp = ConfiguracaoApp.findAll(ConfiguracaoApp.class)
					.next();
		}

		return "http://" + sConfiguracaoApp.getIpConexao() + ":"
				+ sConfiguracaoApp.getPortaDeConecao()
				+ "/ifature/rest/closeRouteBean/";

		// return "http://192.168.0.10:8080/iFature/rest/exportBean/";
	}

	public static String getAccessURL() {

		if (sConfiguracaoApp == null) {
			sConfiguracaoApp = ConfiguracaoApp.findAll(ConfiguracaoApp.class)
					.next();
		}

		return "http://" + sConfiguracaoApp.getIpConexao() + ":"
				+ sConfiguracaoApp.getPortaDeConecao() + "/"
				+ sConfiguracaoApp.getNomeServicoWebAutenticacao() + "/";
	}

	/**
	 *
	 * Método utilizado nas cargas de dados utilizando AsyncTask (a
	 * implementação da task deve extender ProgressAsyncTask para que o método
	 * publishProgress seja visível externamente).
	 *
	 * Internamente o método também persiste os dados consultados usando o
	 * comando insert e os nomes do campos que devem ser passados nos parâmetro
	 * 'sqlStament' e 'mapCampos', respectivamente.
	 *
	 * @param url
	 * @param method
	 * @param params
	 * @param database
	 * @param sqlStament
	 * @param mapCampos
	 * @param mHandler
	 * @param numeroTabela
	 * @param nomeTabela
	 * @param qtdRefresh
	 * @throws Exception
	 */
	public void execute(String url, final EnumTipoRequisicao method,
			final List<NameValuePair> params, final SQLiteDatabase database,
			final String sqlStament, final Map<Integer, String> mapCampos,
			final ProgressAsyncTask task, final Integer numeroTabela,
			final Integer nomeTabela, final Integer qtdRefresh)
			throws Exception {
		InputStream is = null;
		try {
			boolean isChamadaConcluida = Boolean.FALSE;

			atualizarProgresso(task, numeroTabela, 0, 0, nomeTabela);

			switch (method) {
			case GET: {
				final DefaultHttpClient httpClient = new DefaultHttpClient();
				final String paramString = URLEncodedUtils.format(params,
						"utf-8");
				url += "?" + paramString;

				final HttpGet httpGet = new HttpGet(url);
				final HttpResponse httpResponse = httpClient.execute(httpGet);
				final HttpEntity httpEntity = httpResponse.getEntity();

				is = processarRespostaHttp(httpResponse, httpEntity);

				isChamadaConcluida = Boolean.TRUE;
				break;
			}
			case POST: {
				final DefaultHttpClient httpClient = new DefaultHttpClient();
				final HttpPost httpPost = new HttpPost(url);

				httpPost.setEntity(new UrlEncodedFormEntity(params));

				final HttpResponse httpResponse = httpClient.execute(httpPost);
				final HttpEntity httpEntity = httpResponse.getEntity();

				is = processarRespostaHttp(httpResponse, httpEntity);

				isChamadaConcluida = Boolean.TRUE;
				break;
			}
			}

			if (isChamadaConcluida && is != null) {
				final StringBuilder inputStreamToString = inputStreamToString(is);

				if (inputStreamToString != null
						&& !inputStreamToString.toString().trim().isEmpty()) {
					if (database != null) {

						database.beginTransaction();

						final SQLiteStatement stmt = database
								.compileStatement(sqlStament);

						final Object json = new JSONTokener(
								inputStreamToString.toString()).nextValue();

						if (json != null) {

							/*
							 * Caso o retorno seja uma lista.
							 */
							if (json instanceof JSONArray) {
								final JSONArray jsonArray = (JSONArray) json;

								atualizarProgresso(task, numeroTabela, 0,
										jsonArray.length(), nomeTabela);

								for (int posicaoMsg = 0; posicaoMsg < jsonArray
										.length(); posicaoMsg++) {

									final JSONObject jsonObject = jsonArray
											.getJSONObject(posicaoMsg);

									executeInsert(mapCampos, stmt, posicaoMsg,
											jsonObject);

									/*
									 * Atualizar as barras de progresso.
									 */
									if (qtdRefresh != null
											&& ((posicaoMsg % qtdRefresh) == 0)
											|| (posicaoMsg + 1 == jsonArray
													.length())) {
										atualizarProgresso(task, numeroTabela,
												posicaoMsg + 1,
												jsonArray.length(), nomeTabela);
									}
								}

								stmt.close();
							}

							/*
							 * Caso o retorno seja um objeto.
							 */
							else if (json instanceof JSONObject) {

								final JSONObject jsonObject = (JSONObject) json;

								executeInsert(mapCampos, stmt, 0, jsonObject);

								atualizarProgresso(task, numeroTabela, 1, 1,
										nomeTabela);
							}

							database.setTransactionSuccessful();

							stmt.close();
						}

						stmt.close();
					}
				}
			}
		}
		catch (final HttpHostConnectException e) {
			Log.e("Web-Service", e.getLocalizedMessage());
			throw new Exception(SERVICO_INDISPONIVEL);
		}
		catch (final Exception e) {
			Log.e("Web-Service", e.getLocalizedMessage());
			throw new Exception(ERRO_GENERICO);
		}
		finally {
			if (database != null && database.inTransaction()) {
				database.endTransaction();
			}

			if (is != null) {
				is.close();
			}
		}
	}

	/**
	 *
	 * @param url
	 * @param method
	 * @param params
	 * @return
	 * @throws Exception
	 */
	public Object execute(String url, final EnumTipoRequisicao method,
			final List<NameValuePair> params) throws Exception {
		InputStream is = null;
		try {
			boolean isChamadaConcluida = Boolean.FALSE;

			List<Object> lstRetorno = null;

			switch (method) {
			case GET: {
				final DefaultHttpClient httpClient = new DefaultHttpClient();

				final String paramString = URLEncodedUtils.format(params,
						"utf-8");

				url += "?" + paramString;

				final HttpGet httpGet = new HttpGet(url);
				final HttpResponse httpResponse = httpClient.execute(httpGet);
				final HttpEntity httpEntity = httpResponse.getEntity();

				is = processarRespostaHttp(httpResponse, httpEntity);

				isChamadaConcluida = Boolean.TRUE;
				break;
			}
			case POST: {
				final DefaultHttpClient httpClient = new DefaultHttpClient();

				final HttpPost httpPost = new HttpPost(url);

				httpPost.setEntity(new UrlEncodedFormEntity(params));

				final HttpResponse httpResponse = httpClient.execute(httpPost);
				final HttpEntity httpEntity = httpResponse.getEntity();

				is = processarRespostaHttp(httpResponse, httpEntity);

				isChamadaConcluida = Boolean.TRUE;
				break;
			}
			}

			if (isChamadaConcluida && is != null) {
				final StringBuilder inputStreamToString = inputStreamToString(is);

				if (inputStreamToString != null
						&& inputStreamToString.toString() != null
						&& !inputStreamToString.toString().trim().isEmpty()) {
					final Object json = new JSONTokener(
							inputStreamToString.toString()).nextValue();

					lstRetorno = new ArrayList<Object>();

					/*
					 * Caso o retorno seja uma lista.
					 */
					if (json instanceof JSONArray) {
						final JSONArray jsonArray = (JSONArray) json;

						for (int posicaoMsg = 0; posicaoMsg < jsonArray
								.length(); posicaoMsg++) {
							Object jsonObject = null;

							if (jsonArray.get(posicaoMsg) instanceof JSONObject) {
								jsonObject = jsonArray
										.getJSONObject(posicaoMsg);
							}
							else {
								jsonObject = jsonArray.get(posicaoMsg);
							}

							lstRetorno.add(jsonObject);
						}
					}
					/*
					 * Caso o retorno seja um objeto.
					 */
					else if (json instanceof JSONObject) {
						final JSONObject jsonObject = (JSONObject) json;

						lstRetorno.add(jsonObject);
					}
					else if (json != null) {
						lstRetorno.add(json);
					}
				}
			}

			return lstRetorno;
		}
		catch (final HttpHostConnectException e) {
			Log.e("Web-Service", e.getLocalizedMessage());
			throw new Exception(SERVICO_INDISPONIVEL);
		}
		catch (final NetworkOnMainThreadException net) {
			Log.e("Web-Service", net.getLocalizedMessage());
			throw new Exception("Não é possível chamar um serviço de"
					+ " rede direto da thread principal da aplicação.");
		}
		catch (final Exception e) {
			if (e != null) {
				Log.e("Web-Service", e.getLocalizedMessage());
			}
			throw new Exception(ERRO_GENERICO);
		}
		finally {
			if (is != null) {
				is.close();
			}
		}
	}

	private static StringBuilder inputStreamToString(final InputStream is) {
		try {
			String line = "";
			final StringBuilder total = new StringBuilder();

			// Wrap a BufferedReader around the InputStream
			final BufferedReader rd = new BufferedReader(new InputStreamReader(
					is));

			// Read response until the end
			while ((line = rd.readLine()) != null) {
				total.append(line);
			}

			// Return full string
			return total;
		}
		catch (final Exception e) {
			Log.e("RetornoWebService-ConverterString", e.getMessage());
			return null;
		}
	}

	/**
	 *
	 * Método responsável por processar e tratar a resposta http de acordo com o
	 * status retornado pelo webservice.
	 *
	 * @param httpResponse
	 * @param httpEntity
	 * @return
	 * @throws IOException
	 * @throws Exception
	 */
	private InputStream processarRespostaHttp(final HttpResponse httpResponse,
			final HttpEntity httpEntity) throws IOException, Exception {
		InputStream is = null;

		switch (httpResponse.getStatusLine().getStatusCode()) {
		case HttpStatus.SC_OK: {
			is = httpEntity.getContent();
			break;
		}
		case HttpStatus.SC_NO_CONTENT: {
			break;
		}
		case HttpStatus.SC_UNAUTHORIZED:
		case HttpStatus.SC_PRECONDITION_FAILED: {
			throw new Exception(EntityUtils.toString(httpEntity));
		}
		case HttpStatus.SC_NOT_FOUND: {
			throw new Exception(SERVICO_INDISPONIVEL);
		}
		default: {
			throw new Exception(ERRO_GENERICO);
		}
		}

		return is;
	}

	/**
	 * Verifica a disponibilidade do serviço web.
	 *
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public static boolean webServiceIsAvailable(int typeRequest)
			throws Exception {
		// List<Boolean> retorno = null;

		String urlMetodo = null;

		switch (typeRequest) {
		case REQUEST_TYPE_IMPORT: {
			// retorno = (List<Boolean>) new JSonHttpRequest().execute(
			// getImportURL() + "ping", EnumTipoRequisicao.POST,
			// new ArrayList<NameValuePair>());
			urlMetodo = getImportURL() + "ping";
			break;
		}
		case REQUEST_TYPE_EXPORT: {
			// retorno = (List<Boolean>) new JSonHttpRequest().execute(
			// getExportURL() + "ping", EnumTipoRequisicao.POST,
			// new ArrayList<NameValuePair>());

			urlMetodo = getExportURL() + "ping";
			break;
		}
		case REQUEST_TYPE_ACCESS: {

			urlMetodo = getAccessURL() + "ping";
			break;
			// retorno = (List<Boolean>) new JSonHttpRequest().execute(
			// getAccessURL() + "ping", EnumTipoRequisicao.POST,
			// new ArrayList<NameValuePair>());
		}
		case REQUEST_TYPE_CLOSE: {
			// retorno = (List<Boolean>) new JSonHttpRequest().execute(
			// getClouseRouteURL() + "ping", EnumTipoRequisicao.POST,
			// new ArrayList<NameValuePair>());

			urlMetodo = getClouseRouteURL() + "ping";
			break;
		}
		}

		// if (retorno != null && !retorno.isEmpty()) {
		// return retorno.iterator().next();
		// }

		boolean isDisponivel = requestWebServicePing(urlMetodo);

		return isDisponivel;
	}

	public static boolean requestWebServicePing(String serviceUrl)
			throws Exception {

		disableConnectionReuseIfNecessary();

		HttpURLConnection urlConnection = null;

		try {
			// create connection
			URL urlToRequest = new URL(serviceUrl);
			urlConnection = (HttpURLConnection) urlToRequest.openConnection();
			urlConnection.setConnectTimeout(2000);
			urlConnection.setReadTimeout(2000);

			// TODO Tratar o getResponseCode quando for um post method.

			// handle issues
			int statusCode = urlConnection.getResponseCode();

			if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
				// handle unauthorized (if service requires user login)
			}
			else if (statusCode != HttpURLConnection.HTTP_OK) {
				// handle any other errors, like 404, 500,..
			}
			else if (statusCode == HttpURLConnection.HTTP_NOT_FOUND) {
				throw new Exception("Recurso solicitado não está disponível. "
						+ "Informe ao administrador do sistema.");
			}

			// create JSON object from content
			InputStream in = new BufferedInputStream(
					urlConnection.getInputStream());

			return Boolean.valueOf(getResponseText(in));
		}
		catch (MalformedURLException e) {
			// URL is invalid
			Log.e("Web-Service", e.getMessage());
			throw new Exception(
					"A URL informada não existe, ou não está disponível.");
		}
		catch (SocketTimeoutException e) {
			// data retrieval or connection timed out
			Log.e("Web-Service", e.getMessage());
			throw new Exception(SERVICO_INDISPONIVEL);
		}
		catch (IOException e) {
			// could not read response body
			// (could not create input stream)
			Log.e("Web-Service", e.getMessage());
			throw new Exception(
					"Conteudo retorno está inválido, informe ao administrador do sistema.");
		}
		catch (JSONException e) {
			// response body is no valid JSON string
			Log.e("Web-Service", e.getMessage());
			throw new Exception(
					"Conteudo retorno está inválido, informe ao administrador do sistema.");
		}
		finally {
			if (urlConnection != null) {
				urlConnection.disconnect();
			}
		}
	}

	/**
	 * required in order to prevent issues in earlier Android version.
	 */
	private static void disableConnectionReuseIfNecessary() {
		// see HttpURLConnection API doc
		if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
			System.setProperty("http.keepAlive", "false");
		}
	}

	private static String getResponseText(InputStream inStream) {
		// very nice trick from
		// http://weblogs.java.net/blog/pat/archive/2004/10/stupid_scanner_1.html
		return new Scanner(inStream).useDelimiter("\\A").next();
	}

	/**
	 * Executa um insert na base de dados utilizando o um mapa indexado com nome
	 * da coluna e os valores obtidos via web service..
	 *
	 * @param mapCampos
	 *            Map com os campos da tabela.
	 * @param stmt
	 *            Sql de insert da tabela.
	 * @param posicaoMsg
	 *            Posição inicial do insert.
	 * @param jsonObject
	 *            Object carregado do web service.
	 * @throws JSONException
	 *             Erro campo encontre.
	 */
	private void executeInsert(final Map<Integer, String> mapCampos,
			final SQLiteStatement stmt, final int posicaoMsg,
			final JSONObject jsonObject) throws JSONException {

		// Inserir os dados no banco. Coluna _ID.
		stmt.bindString(1, String.valueOf(posicaoMsg + 1));

		// Campos da tabela passados como parâmetro.
		for (final Integer chave : mapCampos.keySet()) {
			final String valorCampo = jsonObject
					.getString(mapCampos.get(chave));

			if (valorCampo != null && valorCampo.equalsIgnoreCase("true")) {
				stmt.bindLong(chave, 1L);
			}
			else if (valorCampo != null && valorCampo.equalsIgnoreCase("false")) {
				stmt.bindLong(chave, 0L);
			}
			else if (valorCampo != null && valorCampo.equalsIgnoreCase("null")) {
				stmt.bindNull(chave);
			}
			else {
				stmt.bindString(chave, valorCampo);
			}
		}

		// A cada regitro de retorno passar para o banco.
		stmt.execute();
		stmt.clearBindings();
	}

	/**
	 * Enviar arquivos de um determinado diretório de origem.
	 *
	 * @param sourceDir
	 * @param params
	 * @throws Exception
	 */
	public void sendFiles(String sourceDir, String moveTo,
			final List<NameValuePair> params, final ProgressAsyncTask task,
			final Integer numeroTabela, final Integer nomeTabela)
			throws Exception {

		atualizarProgresso(task, numeroTabela, 0, 0, nomeTabela);

		try {

			final File[] files = FileUtils.getListFiles(sourceDir);

			if (files != null && files.length > 0) {

				SoapHttpRequest request = new SoapHttpRequest();

				atualizarProgresso(task, numeroTabela, 0, files.length,
						nomeTabela);

				for (int posicao = 0; posicao < files.length; posicao++) {

					request.execute(FileUtils.toByteArray(files[posicao]),
							files[posicao].getName(), params);

					if (moveTo != null) {
						FileUtils.move(files[posicao], moveTo);
					}

					atualizarProgresso(task, numeroTabela, posicao + 1,
							files.length, nomeTabela);
				}
			}
		}
		catch (Exception e) {
			Log.e("Web-Service-Soap", e.getLocalizedMessage());
			throw new Exception(ERRO_GENERICO);
		}
	}

	/**
	 *
	 * Informa os dados necessários para a atualização das barras de progresso.
	 *
	 * @param task
	 * @param tabela
	 * @param posicao
	 * @param totalRegistros
	 * @param nomeTabela
	 */
	public void atualizarProgresso(final ProgressAsyncTask task,
			final Integer tabela, final int posicao, final int totalRegistros,
			final Integer nomeTabela) {
		if (task != null) {
			task.myPublishProgress(EnumProgress.EM_ANDAMENTO.getType(), tabela,
					posicao, totalRegistros, nomeTabela);
		}
	}
}